/*
 * Copyright 2004-2014 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 * You may not use this work except in compliance with the Licence.
 * You may obtain a copy of the Licence at:
 *
 * http://ec.europa.eu/idabc/eupl
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the Licence for the specific language governing permissions and limitations
 * under the Licence.
*/

package com.eviware.soapui.impl.wsdl.support.wsdl;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.NTCredentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.params.AuthPolicy;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.support.CompressionSupport;
import com.eviware.soapui.impl.wsdl.support.PathUtils;
import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport;
import com.eviware.soapui.impl.wsdl.support.http.ProxyUtils;
import com.eviware.soapui.model.ModelItem;
import com.eviware.soapui.model.propertyexpansion.DefaultPropertyExpansionContext;
import com.eviware.soapui.model.settings.Settings;
import com.eviware.soapui.settings.HttpSettings;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.swing.SwingWorker;
import com.eviware.soapui.support.types.StringToStringMap;
import com.eviware.x.form.XForm;
import com.eviware.x.form.XFormDialog;
import com.eviware.x.form.XFormDialogBuilder;
import com.eviware.x.form.XFormFactory;

/**
 * WsdlLoader for URLs
 *
 * @author ole.matzura
 */

public class UrlWsdlLoader extends WsdlLoader {
    private HttpContext state;
    protected HttpGet getMethod;
    private boolean aborted;
    protected Map<String, byte[]> urlCache = new HashMap<String, byte[]>();
    protected boolean finished;
    private boolean useWorker;
    private ModelItem contextModelItem;
    private org.apache.http.HttpResponse httpResponse;

    public UrlWsdlLoader(String url) {
        this(url, null);
    }

    public UrlWsdlLoader(String url, ModelItem contextModelItem) {
        super(url);
        this.contextModelItem = contextModelItem;
        state = new BasicHttpContext();
    }

    public boolean isUseWorker() {
        return useWorker;
    }

    public void setUseWorker(boolean useWorker) {
        this.useWorker = useWorker;
    }

    public InputStream load() throws Exception {
        return load(getBaseURI());
    }

    public synchronized InputStream load(String url) throws Exception {
        if (!PathUtils.isHttpPath(url)) {
            try {
                File file = new File(url.replace('/', File.separatorChar));
                if (file.exists()) {
                    url = file.toURI().toURL().toString();
                }
            } catch (Exception e) {
            }
        }

        if (urlCache.containsKey(url)) {
            setNewBaseURI(url);
            return new ByteArrayInputStream(urlCache.get(url));
        }

        if (url.startsWith("file:")) {
            return handleFile(url);
        }

        log.debug("Getting wsdl component from [" + url + "]");

        createGetMethod(url);

        if (aborted) {
            return null;
        }

        LoaderWorker worker = new LoaderWorker();
        if (useWorker) {
            worker.start();
        } else {
            worker.construct();
        }

        while (!aborted && !finished) {
            Thread.sleep(200);
        }

        // wait for method to catch up - required in unit tests..
        // limited looping to 10 loops because of eclipse plugin which entered
        // endless loop without it
        int counter = 0;
        byte[] content = null;

        if (httpResponse != null && httpResponse.getEntity() != null) {
            content = EntityUtils.toByteArray(new BufferedHttpEntity(httpResponse.getEntity()));
        }

        while (!aborted && content == null && counter < 10) {
            Thread.sleep(200);
            counter++;
        }

        if (aborted) {
            throw new Exception("Load of url [" + url + "] was aborted");
        } else {
            if (content != null) {
                String compressionAlg = HttpClientSupport.getResponseCompressionType(httpResponse);
                if (compressionAlg != null) {
                    content = CompressionSupport.decompress(compressionAlg, content);
                }

                urlCache.put(url, content);
                String newUrl = getMethod.getURI().toString();
                if (!url.equals(newUrl)) {
                    log.info("BaseURI was redirected to [" + newUrl + "]");
                }
                setNewBaseURI(newUrl);
                urlCache.put(newUrl, content);
                return new ByteArrayInputStream(content);
            } else {
                throw new Exception("Failed to load url; " + url + ", "
                        + (httpResponse != null ? httpResponse.getStatusLine().getStatusCode() : 0) + " - "
                        + (httpResponse != null ? httpResponse.getStatusLine().getReasonPhrase() : ""));
            }
        }
    }

    protected InputStream handleFile(String url) throws Exception {
        setNewBaseURI(url);
        return new URL(url).openStream();
    }

    protected void createGetMethod(String url) {
        getMethod = new HttpGet(url);
        getMethod.getParams().setParameter(ClientPNames.HANDLE_REDIRECTS, true);
        state.setAttribute(ClientContext.CREDS_PROVIDER, new WsdlCredentialsProvider());

        if (SoapUI.getSettings().getBoolean(HttpSettings.AUTHENTICATE_PREEMPTIVELY)) {
            if (!StringUtils.isNullOrEmpty(getUsername()) && !StringUtils.isNullOrEmpty(getPassword())) {
                UsernamePasswordCredentials creds = new UsernamePasswordCredentials(getUsername(), getPassword());
                getMethod.addHeader(BasicScheme.authenticate(creds, "utf-8", false));
            }
        }
    }

    public final class LoaderWorker extends SwingWorker {
        public Object construct() {
            HttpClientSupport.SoapUIHttpClient httpClient = HttpClientSupport.getHttpClient();
            try {
                Settings soapuiSettings = SoapUI.getSettings();

                HttpClientSupport.applyHttpSettings(getMethod, soapuiSettings);

                httpResponse = httpClient.execute(getMethod, state);
            } catch (Exception e) {
                return e;
            } finally {
                finished = true;
            }

            return null;
        }
    }

    public boolean abort() {
        if (getMethod != null) {
            getMethod.abort();
        }

        aborted = true;

        return true;
    }

    public boolean isAborted() {
        return aborted;
    }

    /**
     * CredentialsProvider for providing login information during WSDL loading
     *
     * @author ole.matzura
     */

    private static Map<AuthScope, Credentials> cache = new HashMap<AuthScope, Credentials>();

    public final class WsdlCredentialsProvider implements CredentialsProvider {
        private XFormDialog basicDialog;
        private XFormDialog ntDialog;

        public WsdlCredentialsProvider() {
        }

        public Credentials getCredentials(final AuthScope authScope) {
            if (authScope == null) {
                throw new IllegalArgumentException("Authentication scope may not be null");
            }

            //	if( cache.containsKey( authScope ) )
            //	{
            //	return cache.get( authScope );
            //	}

            String pw = getPassword();
            if (pw == null) {
                pw = "";
            }

            if (AuthPolicy.NTLM.equalsIgnoreCase(authScope.getScheme())
                    || AuthPolicy.SPNEGO.equalsIgnoreCase(authScope.getScheme())) {
                String workstation = "";
                try {
                    workstation = InetAddress.getLocalHost().getHostName();
                } catch (UnknownHostException e) {
                }

                if (hasCredentials()) {
                    log.info("Returning url credentials");
                    return new NTCredentials(getUsername(), pw, workstation, null);
                }

                log.info(authScope.getHost() + ":" + authScope.getPort() + " requires Windows authentication");
                if (ntDialog == null) {
                    buildNtDialog();
                }

                StringToStringMap values = new StringToStringMap();
                values.put("Info", "Authentication required for [" + authScope.getHost() + ":" + authScope.getPort() + "]");
                ntDialog.setValues(values);

                if (ntDialog.show()) {
                    values = ntDialog.getValues();
                    NTCredentials credentials = new NTCredentials(values.get("Username"), values.get("Password"),
                            workstation, values.get("Domain"));

                    cache.put(authScope, credentials);
                    return credentials;
                }
            } else if (AuthPolicy.BASIC.equalsIgnoreCase(authScope.getScheme())
                    || AuthPolicy.DIGEST.equalsIgnoreCase(authScope.getScheme())) {
                if (hasCredentials()) {
                    log.info("Returning url credentials");
                    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(getUsername(), pw);
                    cache.put(authScope, credentials);
                    return credentials;
                }

                log.info(authScope.getHost() + ":" + authScope.getPort() + " requires authentication with the realm '"
                        + authScope.getRealm() + "'");
                ShowDialog showDialog = new ShowDialog();
                showDialog.values.put("Info",
                        "Authentication required for [" + authScope.getHost() + ":" + authScope.getPort() + "]");

                UISupport.getUIUtils().runInUIThreadIfSWT(showDialog);
                if (showDialog.result) {
                    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
                            showDialog.values.get("Username"), showDialog.values.get("Password"));
                    cache.put(authScope, credentials);
                    return credentials;
                }
            }

            return null;
        }

        private void buildBasicDialog() {
            XFormDialogBuilder builder = XFormFactory.createDialogBuilder("Basic Authentication");
            XForm mainForm = builder.createForm("Basic");
            mainForm.addLabel("Info", "");
            mainForm.addTextField("Username", "Username for authentication", XForm.FieldType.TEXT);
            mainForm.addTextField("Password", "Password for authentication", XForm.FieldType.PASSWORD);

            basicDialog = builder.buildDialog(builder.buildOkCancelActions(), "Specify Basic Authentication Credentials",
                    UISupport.OPTIONS_ICON);
        }

        private void buildNtDialog() {
            XFormDialogBuilder builder = XFormFactory.createDialogBuilder("NT Authentication");
            XForm mainForm = builder.createForm("Basic");
            mainForm.addLabel("Info", "");
            mainForm.addTextField("Username", "Username for authentication", XForm.FieldType.TEXT);
            mainForm.addTextField("Password", "Password for authentication", XForm.FieldType.PASSWORD);
            mainForm.addTextField("Domain", "NT Domain for authentication", XForm.FieldType.TEXT);

            ntDialog = builder.buildDialog(builder.buildOkCancelActions(), "Specify NT Authentication Credentials",
                    UISupport.OPTIONS_ICON);
        }

        private class ShowDialog implements Runnable {
            StringToStringMap values = new StringToStringMap();
            boolean result;

            public void run() {
                if (basicDialog == null) {
                    buildBasicDialog();
                }

                basicDialog.setValues(values);

                result = basicDialog.show();
                if (result) {
                    values = basicDialog.getValues();
                }
            }
        }

        public void clear() {
            cache.clear();
        }

        public void setCredentials(AuthScope arg0, Credentials arg1) {
        }
    }

    public void close() {
    }
}
